﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using MvcApplication3.Models;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.OpenId.RelyingParty;
using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
using DotNetOpenAuth;
using System.Net;
using System.IO;
using System.Text;
using System.Runtime.Serialization.Json;
using System.Diagnostics;
using System.Net.Security;

namespace MvcApplication3.Controllers
{

    [HandleError]
    public class AccountController : Controller
    {
        public static agent useragent;

        public IFormsAuthenticationService FormsService { get; set; }
        public IMembershipService MembershipService { get; set; }
        private static OpenIdRelyingParty openid = new OpenIdRelyingParty();
        protected override void Initialize(RequestContext requestContext)
        {
            if (FormsService == null) { FormsService = new FormsAuthenticationService(); }
            if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
            base.Initialize(requestContext);
        }
        [HttpGet]
        [ActionName("FacebookLogin")]
        public ActionResult FacebookLogin()
        {
            useragent = agent.facebook;
            string redirecturl = Url.RouteUrl("Default", new { action = "Handshake" }, "http");
            Debug.WriteLine(redirecturl);
            //this is the intial auth request url and query string for facebook, redirect_uri should be a dynamice domain lookup, however we had to hardcode 
            // the redirect because of the massey proxy when using this on a real public domain it should be possible to use the mvc routing to generate the urls
            // like so: string redirecturl = Url.RouteUrl("Default", new { action = "Handshake" }, "http");
            return new RedirectResult("https://graph.facebook.com/oauth/authorize?type=web_server&client_id=229468770456314&redirect_uri=http://localhost.massey.ac.nz:5188/Account/Handshake&scope=email&auth_type=reauthenticate");
        }
        [HttpGet]
        [ActionName("LiveLogin")]
        public ActionResult LiveLogin()
        {
           useragent= agent.live;

            //practically the same as the facebook request except for the scope which allows you define what info you want wl.emails, gets the basic public information 
            // plus any linked email accounts
           return new RedirectResult("https://oauth.live.com/authorize?client_id=000000004007C817&scope=wl.emails&response_type=code&redirect_uri=http://localhost.massey.ac.nz:5188/Account/Handshake");
        }

        //this is the statically typed representation of the JSON object that will get returned from: https://graph.facebook.com/me
        public class FacebookUsername
        {   
            public long id { get; set; } //yes. the user id is of type long...dont use int
            public string first_name { get; set; }
            public string last_name { get; set; }
            public string name { get; set; }
            public string email { get; set; }

        }
        public class liveResponse
        {
            /* the class for the Live ID JSON initial Oauth response deserialization
             * 
             * */
            public string access_token { get; set; }
            public string scope { get; set; }
            public string expires_in { get; set; }
            public string token_type { get; set; }
            public string authentication_token { get; set; }

            //access_token, scope, expires_in, token_type, authentication_token
        }
        public class email
        {/* msn Live ID allows multiple emails linked to the one account, these are returned as JSON from MSN, then it is decoded 
           * in the handshake flow into an object which takes this form*/
            public string prefered { get; set; }
            public string account { get; set; }
            public string personal { get; set; }
            public string business { get; set; }
        }
        public class liveUser
        {   /* the class for the Live ID JSON response deserialization
             * 
             * */
            public string id { get; set; }
            public string name { get; set; }
            public string first_name { get; set; }
            public string last_name { get; set; }
            public string link { get; set; }
            public string gender { get; set; }
            public email  emails{ get; set; } 
            public string locale { get; set; }
            //access_token, scope, expires_in, token_type, authentication_token
        }
        
        public enum agent
        {
            live,
            facebook,
            google,
            yahoo
        }

        public ActionResult XRDS()
        {
            return View();
        }

        /*the hadshake method is autentication flow for provider which use OpenAUth 'like' authentication ; facebook and msn live ID
        
         * this controller action will be called when Facebook or Live ID redirects*/
        [HttpGet]
        [ActionName("handshake")]
        public ActionResult Handshake(string code)
        {
            Debug.WriteLine(code);
            //after authentication, Facebook will redirect to this controller action with a QueryString parameter called "code" (this is Facebook's Session key)
              string clientId = "";
              string clientSecret = "";
              string url = "";
              string redirectUri = "http://localhost.massey.ac.nz:5188/Account/Handshake";
             // agent useragent = (agent) Session["agent"];
            switch (useragent)
            {
                case agent.facebook:
                    //OpenAuth protocols use a shared secret for the decrpytion protocol messages and user info
                     clientId = "229468770456314";
                     clientSecret = "0da8c9fc135dd9bc2f18bf871f3faf58";
                     url = "https://graph.facebook.com/oauth/access_token?client_id={0}&redirect_uri={1}&client_secret={2}&code={3}";
                    break;
                case agent.live:
                     clientId = "000000004007C817";
                     clientSecret = "eMhJT5nHPeFp3XYF75KpFuoUbVFFYBSE";
                     url = "https://oauth.live.com/token?client_id={0}&redirect_uri={1}&client_secret={2}&code={3}&grant_type=authorization_code";
                    break;
            }
            WebRequest request = WebRequest.Create(string.Format(url, clientId, redirectUri, clientSecret, code));
            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();
            Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
            StreamReader streamReader = new StreamReader(stream, encode);
            liveResponse users = new liveResponse();
            string accessToken = "";
            switch (useragent){
                case agent.live:
                    DataContractJsonSerializer jsonObject = new DataContractJsonSerializer(typeof(liveResponse));
                    users = jsonObject.ReadObject(stream) as liveResponse;
                    break;
                case agent.facebook:
                    accessToken = streamReader.ReadToEnd().Replace("access_token=", "");
                    string[] expiry = accessToken.Split('&');
                    for (int i = 0; i < expiry.Length; i++)
                    {
                        if (expiry[i].Contains("expires"))
                        {
                            string test = expiry[i].Substring(expiry[i].IndexOf('='));
                           Session["expiry"] = Convert.ToDouble( expiry[i].Substring(8));
                           //set the access token to some session variable so it can be used through out the session
                           Session["AccessToken"] = accessToken;
                        }
                    }
                    break;
            }
            response.Close();
            streamReader.Close();
            FacebookUsername fbuser = new FacebookUsername();
            switch (useragent)
            {
                case agent.facebook:
                    url = "https://graph.facebook.com/me?access_token={0}";
                    request = WebRequest.Create(string.Format(url, accessToken));
                    response = request.GetResponse();
                    stream = response.GetResponseStream();
                    //now the stream is open read the JSON response and deserialize into a FacebookUsername object
                    DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(FacebookUsername));
                    fbuser = dataContractJsonSerializer.ReadObject(stream) as FacebookUsername;
                    //close the stream
                    response.Close();
                    fbuser.first_name = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(fbuser.first_name);
                    fbuser.last_name = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(fbuser.last_name);
                    fbuser.name = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(fbuser.name);
                    //set session cookies to some user credentials
                    Session["email"] = fbuser.email;
                    Session["claimid"] = fbuser.id;
                    Session["name"] = fbuser.first_name + " " + fbuser.last_name;
                    //Session["expiry"] is set further u.
                    break;
                case agent.live:
                    // this is where you have already obtained the access token, you can now user it to access extra info such as email name etc
                    url = "https://apis.live.net/v5.0/me?access_token={0}";
                    request = WebRequest.Create(string.Format(url, users.access_token));
                    response = request.GetResponse();
                    stream = response.GetResponseStream();
                    //now the stream is open read the JSON response and deserialize into a liveUser object
                    StreamReader liveIDStream = new StreamReader(stream, encode);
                    DataContractJsonSerializer dataContractJsonSerializer1 = new DataContractJsonSerializer(typeof(liveUser));
                    streamReader = new StreamReader(stream, encode);                    
                    liveUser userInfo = new liveUser();
                    userInfo = dataContractJsonSerializer1.ReadObject(stream) as liveUser;
                    // close the stream                                   
                    liveIDStream.Close();
                    response.Close();
                    //next line just ensures a capital letter on the name 
                    if (userInfo.first_name != null)
                    userInfo.first_name = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(userInfo.first_name);
                    if (userInfo.last_name != null)
                    userInfo.last_name = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(userInfo.last_name);
                    if (userInfo.name != null)
                    userInfo.name = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(userInfo.name);
                    Session["email"] = userInfo.emails.account;
                    Session["claimid"] = userInfo.id;
                    Session["name"] = userInfo.name;
                    Session["expiry"] = users.expires_in;
                    //Debug.WriteLine(Session["expiry"]);
                    break;
            }
            FormsAuthentication.Initialize();
            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, Session["claimid"].ToString(), DateTime.Now, DateTime.Now.AddSeconds(Convert.ToDouble(Session["expiry"])), false, "");
            string hash = FormsAuthentication.Encrypt(ticket);
            HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
            cookie.Expires = ticket.Expiration;
            Response.Cookies.Add(cookie);
            return RedirectToAction("memberArea", "Account");
        }
        
        /* the authenticate method is not a global authenticate, it is the authentication for the providers which conform to the open-id
         * standard ; yahoo and google in  this case*/
        [ValidateInput(false)]
        public ActionResult Authenticate(string returnUrl)
        {  //get the openid response, if it doen't exist create a request to the selected provider, which after the user has authenticated, will redirect 
            //here and then the response can be checked
            var response = openid.GetResponse();
            if (response == null)
            {
                // Stage 2: user submitting Identifier  
                Identifier id;
                if (Identifier.TryParse(Request.Form["openid_identifier"], out id))
                {
                    try
                    {
                        if(Request.Form["openid_identifier"].Contains("google.com"))
                            useragent = agent.google;
                        if (Request.Form["openid_identifier"].Contains("yahoo.com"))
                            useragent = agent.yahoo;
                        var request = openid.CreateRequest(Request.Form["openid_identifier"]);
                        //Ask user for their email address  
                        var fetch = new FetchRequest();
                        var pape = new DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy.PolicyRequest();
                        pape.MaximumAuthenticationAge = TimeSpan.Zero;
                        
                        fetch.Attributes.AddRequired(WellKnownAttributes.Name.First);
                        fetch.Attributes.AddRequired(WellKnownAttributes.Name.Last);
                        fetch.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
                        fetch.Attributes.AddRequired(WellKnownAttributes.Contact.HomeAddress.Country);
                        DotNetOpenAuth.XrdsPublisher xrds = new DotNetOpenAuth.XrdsPublisher();
                        request.AddExtension(pape);
                        request.AddExtension(fetch);
                        return request.RedirectingResponse.AsActionResult();
                    }
                    catch (ProtocolException ex)
                    {
                        ViewData["Message"] = ex.Message;
                        return View("LogOn");
                    }
                }

                ViewData["Message"] = "Invalid identifier";
                return View("LogOn");
            }

            // Stage 3: OpenID Provider sending assertion response  
            switch (response.Status)
            {  // if the response is valid and the user is autheticated, try to access their credentials and claimed_id for storage and session creating
                case AuthenticationStatus.Authenticated:
                    var tmp = response.GetExtension<FetchResponse>();
                    String email = "";
                    String claimed = "";
                    String secondName = "";
                    String firstName = "";
                    if (tmp != null)
                    {
                        if (tmp.GetAttributeValue(WellKnownAttributes.Contact.Email) != null)
                        email = tmp.GetAttributeValue(WellKnownAttributes.Contact.Email);
                        if (tmp.GetAttributeValue(WellKnownAttributes.Name.First) != null)
                        firstName = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(tmp.GetAttributeValue(WellKnownAttributes.Name.First));
                        if (tmp.GetAttributeValue(WellKnownAttributes.Name.Last) != null)
                        secondName = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(tmp.GetAttributeValue(WellKnownAttributes.Name.Last));
                        claimed = response.ClaimedIdentifier;
                    }
                    Session["email"] = email;
                    Session["claimid"] = claimed;
                    Session["firstname"] = firstName;
                    if (secondName == "")
                    {
                        if (firstName != "")
                            Session["name"] = firstName;
                        else
                        {
                            Session["name"] = email;
                            firstName = email;
                        }
                    }
                    else
                        Session["name"] = firstName + " " + secondName;
                    Session["secondName"] = secondName;
                    Session["expiry"] = 3600;
                    FormsAuthentication.Initialize();
                    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, Session["claimid"].ToString(), DateTime.Now, DateTime.Now.AddSeconds(3600), false, "");
                    string hash = FormsAuthentication.Encrypt(ticket);
                    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
                    cookie.Expires = ticket.Expiration;
                    Response.Cookies.Add(cookie);
                    return RedirectToAction("memberArea", "Account");
                
                case AuthenticationStatus.Canceled:
                    ViewData["Message"] = "Canceled at provider";
                    return View("LogOn");
                case AuthenticationStatus.Failed:
                    ViewData["Message"] = response.Exception.Message;
                    return View("LogOn");
            }

            return new EmptyResult();
        }

        // **************************************
        // URL: /Account/LogOn
        // **************************************

        public ActionResult LogOn()
        {
            return View();

        }
        [Authorize]
        public ActionResult memberArea()
        {
            return View();
        }
        [HttpPost]
        public ActionResult LogOn(LogOnModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {
                if (MembershipService.ValidateUser(model.UserName, model.Password))
                {
                    FormsService.SignIn(model.UserName, model.RememberMe);
                    if (!String.IsNullOrEmpty(returnUrl))
                    {
                        return Redirect(returnUrl);
                    }
                    else
                    {
                        return RedirectToAction("Index", "Home");
                    }
                }
                else
                {
                    ModelState.AddModelError("", "The user name or password provided is incorrect.");
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

        // **************************************
        // URL: /Account/LogOff
        // **************************************

        public ActionResult LogOff()
        {
            FormsService.SignOut();

            return RedirectToAction("Index", "Home");
        }
        //https://www.google.com/accounts/Logout
       
    }
}
